/**
 * Copyright (c) 2012, Brent Mucci
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **/

#include "mcl_common.h"
#include "mcl_cl_image.h"

#ifdef USE_OPENCL

using namespace mcl;

const char* getClErrorStr(cl_int errorNum)
{
	switch(errorNum)
	{
		// TODO: use actual error numbers
		case  0: return "CL_SUCCESS";
		case  1: return "CL_DEVICE_NOT_FOUND";
		case  2: return "CL_DEVICE_NOT_AVAILABLE";
		case  3: return "CL_COMPILER_NOT_AVAILABLE";
		case  4: return "CL_MEM_OBJECT_ALLOCATION_FAILURE";
		case  5: return "CL_OUT_OF_RESOURCES";
		case  6: return "CL_OUT_OF_HOST_MEMORY";
		case  7: return "CL_PROFILING_INFO_NOT_AVAILABLE";
		case  8: return "CL_MEM_COPY_OVERLAP";
		case  9: return "CL_IMAGE_FORMAT_MISMATCH";
		case 10: return "CL_IMAGE_FORMAT_NOT_SUPPORTED";
		case 11: return "CL_BUILD_PROGRAM_FAILURE";
		case 12: return "CL_MAP_FAILURE";
		case 13: return "";
		case 14: return "";
		case 15: return "";
		case 16: return "";
		case 17: return "";
		case 18: return "";
		case 19: return "";
		case 20: return "";
		case 21: return "";
		case 22: return "";
		case 23: return "";
		case 24: return "";
		case 25: return "";
		case 26: return "";
		case 27: return "";
		case 28: return "";
		case 29: return "";
		case 30: return "CL_INVALID_VALUE";
		case 31: return "CL_INVALID_DEVICE_TYPE";
		case 32: return "CL_INVALID_PLATFORM";
		case 33: return "CL_INVALID_DEVICE";
		case 34: return "CL_INVALID_CONTEXT";
		case 35: return "CL_INVALID_QUEUE_PROPERTIES";
		case 36: return "CL_INVALID_COMMAND_QUEUE";
		case 37: return "CL_INVALID_HOST_PTR";
		case 38: return "CL_INVALID_MEM_OBJECT";
		case 39: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR";
		case 40: return "CL_INVALID_IMAGE_SIZE";
		case 41: return "CL_INVALID_SAMPLER";
		case 42: return "CL_INVALID_BINARY";
		case 43: return "CL_INVALID_BUILD_OPTIONS";
		case 44: return "CL_INVALID_PROGRAM";
		case 45: return "CL_INVALID_PROGRAM_EXECUTABLE";
		case 46: return "CL_INVALID_KERNEL_NAME";
		case 47: return "CL_INVALID_KERNEL_DEFINITION";
		case 48: return "CL_INVALID_KERNEL";
		case 49: return "CL_INVALID_ARG_INDEX";
		case 50: return "CL_INVALID_ARG_VALUE";
		case 51: return "CL_INVALID_ARG_SIZE";
		case 52: return "CL_INVALID_KERNEL_ARGS";
		case 53: return "CL_INVALID_WORK_DIMENSION";
		case 54: return "CL_INVALID_WORK_GROUP_SIZE";
		case 55: return "CL_INVALID_WORK_ITEM_SIZE";
		case 56: return "CL_INVALID_GLOBAL_OFFSET";
		case 57: return "CL_INVALID_EVENT_WAIT_LIST";
		case 58: return "CL_INVALID_EVENT";
		case 59: return "CL_INVALID_OPERATION";
		case 60: return "CL_INVALID_GL_OBJECT";
		case 61: return "CL_INVALID_BUFFER_SIZE";
		case 62: return "CL_INVALID_MIP_LEVEL";
		case 63: return "CL_INVALID_GLOBAL_WORK_SIZE";
		default: break;
	}
	return "UNKNOWN ERROR";
}

void CLImage::loadCLProgramFromFile(const std::string & fileName){
	// Efficiently allocate a string size and read in the file contents
	std::ifstream ifFileStream(fileName);
	ifFileStream.seekg(0, std::ios::end);
	// The tellg() is the pointer position, the +1 is necessary to compensate for pointer position 0.
	size_t size = (size_t)ifFileStream.tellg();
	std::string buffer(size, ' ');
	ifFileStream.seekg(0);
	ifFileStream.read(&buffer[0], size);

	this->m_openCLProgram = new char[strlen(buffer.c_str()) + 1];
	strcpy(this->m_openCLProgram, buffer.c_str());
}

CLImage::CLImage() :
m_sourceWidth(0),
m_sourceHeight(0),
m_destWidth(0),
m_destHeight(0)
{
	m_err = clGetPlatformIDs(1, &m_firstPlatformId, &m_numPlatforms);
	m_err |= clGetDeviceIDs(m_firstPlatformId, CL_DEVICE_TYPE_GPU, 1, &m_device_id, &m_numMatchingDevices);
	this->m_openCLProgram = NULL;
}

CLImage::~CLImage(){
		if(this->m_openCLProgram != NULL){
			delete this->m_openCLProgram;
		}
}

cl_int CLImage::buildProgram(){
	m_context = clCreateContext(0, 1, &m_device_id, NULL, NULL, &m_err);
	m_commands = clCreateCommandQueue(m_context, m_device_id, 0, &m_err);
	m_program = clCreateProgramWithSource(m_context, 1, (const char **) &this->m_openCLProgram, NULL, &m_err);
	m_err = clBuildProgram(m_program, 0, NULL, NULL, NULL, NULL);
	m_kernel = clCreateKernel(m_program, "resizeBilinear", &m_err);
	return m_err;
}

cl_int CLImage::setParameters(
	const unsigned int sourceWidth, const unsigned int sourceHeight, 
	const unsigned int destWidth, const unsigned int destHeight)
{
	// Early exit if parameters don't need to be set
	if(		destWidth == m_destWidth && destHeight == m_destHeight &&
				sourceWidth == m_sourceWidth && sourceHeight == m_sourceHeight )
		return m_err;
	
	m_sourceWidth = sourceWidth;
	m_sourceHeight = sourceHeight;
	m_destWidth = destWidth;
	m_destHeight = destHeight;
	
	this->sourceImage = clCreateBuffer(m_context, CL_MEM_READ_ONLY, (unsigned int)(sizeof(unsigned int) * m_sourceWidth * m_sourceHeight), NULL, NULL);
	this->m_destImage = clCreateBuffer(m_context, CL_MEM_WRITE_ONLY, 4 * destWidth * destHeight, NULL, NULL);
	//m_err = clEnqueueWriteBuffer(m_commands, this->sourceImage, CL_TRUE, 0, m_sourceWidth * m_sourceHeight * 4, sourceImage, 0, NULL, NULL);
	m_err = clSetKernelArg(m_kernel, 0, sizeof(cl_mem), &this->sourceImage);
	m_err |= clSetKernelArg(m_kernel, 1, sizeof(cl_mem), &this->m_destImage);
	m_err |= clSetKernelArg(m_kernel, 2, sizeof(unsigned int), &m_sourceWidth);
	m_err |= clSetKernelArg(m_kernel, 3, sizeof(unsigned int), &m_sourceHeight);
	m_err |= clSetKernelArg(m_kernel, 4, sizeof(unsigned int), &m_destWidth);
	m_err |= clSetKernelArg(m_kernel, 5, sizeof(unsigned int), &m_destHeight);
	m_globalSize[0] = m_destWidth;
	m_globalSize[1] = m_destHeight;
	return m_err;
}

bool CLImage::resizeBilinearOpenCL(const Image &source, Image &dest)
{
	m_err = clEnqueueWriteBuffer(m_commands, this->sourceImage, CL_FALSE, 0, m_sourceWidth * m_sourceHeight * 4, source.data(), 0, NULL, NULL);
	m_err = clEnqueueNDRangeKernel(m_commands, m_kernel, 2, NULL, m_globalSize, NULL , 0, NULL, NULL);
	clFinish(m_commands);
	m_err = clEnqueueReadBuffer(m_commands, m_destImage, CL_TRUE, 0, (int)((dest.getWidth() * dest.getHeight()) * 4), dest.data(), 0, NULL, NULL);
	return ( m_err == CL_SUCCESS );
}

#endif